/************************************************************************

								Heroes IV
					  Copyright 2000, The 3DO Company

	------------------------------------------------------------------
  						       adv_dwelling.cpp

	$Header: /game/adv_dwelling.cpp $

	$NoKeywords: $

 ************************************************************************/

// kill the 255+ character identifier warning.  Templates tend to exceed that easily.
#include "precompiled.h"
#pragma warning( disable: 4786 )

#include "adv_dwelling.h"

#include <cassert>
#include <string>
#include <vector>

#include "adventure_ai.h"
#include "adventure_frame.h"
#include "adventure_map.h"
#include "adventure_sounds.h"
#include "adv_object_type.h"
#include "army.h"
#include "basic_dialog.h"
#include "combat_context_adv_object.h"
#include "combat_context_ptr.h"
#include "combat_result.h"
#include "creature.h"
#include "creature_traits.h"
#include "elements_of.h"
#include "enum_operations.h"
#include "estimate_outcome.h"
#include "external_string.h"
#include "format_string.h"
#include "hero_keyword_replacer.h"
#include "mouse_event.h"
#include "music.h"
#include "object_registration.h"
#include "owned_ptr.h"
#include "player.h"
#include "popup_window.h"
#include "random.h"
#include "recruit_dialog.h"
#include "replace_keywords.h"
#include "run_combat.h"
#include "simple_dialog.h"

// -------------------------------------------------------------------
// Unnamed namespace
// -------------------------------------------------------------------

namespace
{
	int const k_current_map_format_version = 2;
	int const k_current_random_map_format_version = 3;

	int const k_current_save_format_version = 6;

	// Translate t_adv_dwelling's map version to
	// t_owned_scriptable_adv_object's map version
	inline int to_base_class_map_format_version( int format_version )
	{
		return format_version;
	}

	// Translate t_random_adv_dwelling's map version to
	// t_owned_scriptable_adv_object's map version
	inline int random_to_base_class_map_format_version( int format_version )
	{
		if ( format_version == 0 )
			return 0;
		else if ( format_version < 3 )
			return 1;
		else 
			return 2;
	}

	// Translate t_adv_dwelling's save file version to
	// t_owned_scriptable_adv_object's save file version
	inline int to_base_class_save_format_version( int format_version )
	{
		if ( format_version == 0 )
			return 0;
		else if ( format_version < 3 )
			return 1;
		else 
			return 2;
	}

	t_register_with_type< t_adv_dwelling >
		g_dwelling_registration( k_adv_object_creature_dwelling );

	t_object_registration< t_random_adv_dwelling >
		g_random_dwelling_registration( k_adv_object_random_creature_dwelling );

	std::string create_model_name( t_creature_type creature_type )
	{
		assert( creature_type >= 0 && creature_type < k_creature_type_count );

		static char const * const k_model_name[ k_creature_type_count ] =
		{
			"Altar of Air",			// air_elemental
			"Altar of Light",		// angel
			"Ballista Works",		// ballista
			"Den of Thieves",		// bandit
			"Behemoth Crag",		// behemoth
			"PillarOfEyes",			// beholder
			"Dragon Cave",			// black_dragon
			"Dragon Graveyard",		// bone_dragon
			"Centaur Stables",		// centaur
			"Kennels",				// cerberus
			"Knight's Chapter",		// champion
			"Archery Range",		// crossbowman
			"Barracks",				// crusader
			"Cyclops Cave",			// cyclops
			"Spawning Pit",			// venom_spawn
			"Temple of the Damned",	// devil
			"Dragon Factory",		// dragon_golem
			"Dwarven Mines",		// dwarf
			"Altar of Earth",		// earth_elemental
			"Lava Tube",			// efreet
			"Homestead",			// elf
			"Magic Forest",			// faerie_dragon
			"Altar of Fire",		// fire_elemental
			"Parapet",				// gargoyle
			"Altar of Wishes",		// genie
			"Barrow Mound",			// ghost
			"Berserker Dwelling",	// berserker
			"Golem Factory",		// gold_golem
			"Griffin Cliffs",		// griffin
			"Halfling Burrow",		// halfling
			"Harpy Nest",			// harpy
			"Hydra Pond",			// hydra
			"Ice Cave",				// ice_demon
			"Torture Chamber",		// imp
			"Faerie Mound",			// leprechaun
			"Mage Tower",			// mage
			"Mantis Nest",			// mantis
			"Statuary Garden",		// medusa
			"",						// mermaid
			"Minotaur's Maze",		// minotaur
			"Monestary",			// monk
			"Cursed Pyramid",		// mummy
			"Golden Pavilion",		// naga
			"Black Wood",			// nightmare
			"Nomad Tents",			// nomad
			"Ogre Fort",			// ogre_mage
			"Orc Camp",				// orc
			"Hovel",				// peasant
			"Funeral Pyre",			// phoenix
			"Guardhouse",			// pikeman
			"Pirates Cove",			// pirate
			"Enchanted Grove",		// satyr
			"",						// sea_monster
			"Cemetery",				// skeleton
			"Squire's Guild",		// squire
			"Fae Trees",			// sprite
			"Cliff Nest",			// thunderbird
			"Cloud Castle",			// titan
			"Troglodyte Warren",	// troglodyte
			"Troll Cave",			// troll
			"Unicorn Glade",		// unicorn
			"Mansion",				// vampire
			"Altar of Water",		// water_elemental
			"Tiger Den",			// white_tiger
			"Wolf Den",				// wolf
			"Crypt",				// zombie
			"hot_house",			// WaspWort
		};
		assert( k_model_name[ creature_type ][ 0 ] != '\0' );

		return std::string( "creature generators." ) + k_model_name[ creature_type ];
	}

	t_creature_type pick_creature_type( int level )
	{
		assert( level >= 1 && level <= 5 );

		static std::vector< t_creature_type > level_creature_types_array[ 5 ];
		static bool initialized = false;
		if ( !initialized )
		{
			t_creature_type creature_type;
			for (	creature_type = t_creature_type( 0 );
					creature_type < k_creature_type_count;
					enum_incr( creature_type ) )
			{
				if (	creature_type == k_mermaid
					||	creature_type == k_pirate
					||	creature_type == k_sea_monster )
					continue;

				int creature_level = get_traits( creature_type ).level;
				assert( creature_level >= 1 && creature_level <= 5 );

				level_creature_types_array[ creature_level - 1 ].push_back( creature_type );
			}

			initialized = true;
		}

		std::vector< t_creature_type > const & level_creature_types =
			level_creature_types_array[ level - 1 ];
		assert( !level_creature_types.empty() );

		return level_creature_types[ random( level_creature_types.size() ) ];
	}

} // Unnamed namespace

// -------------------------------------------------------------------
// A creature dwelling on the adventure map
// -------------------------------------------------------------------
t_adv_dwelling::t_adv_dwelling( t_creature_type creature_type, t_player_color owner_color )
	:	t_owned_scriptable_adv_object( create_model_name( creature_type ) )
{
	set_player_color( owner_color );
	set_initial_values( creature_type );
}

// -------------------------------------------------------------------
// A creature dwelling on the adventure map
// -------------------------------------------------------------------
t_adv_dwelling::t_adv_dwelling(  std::string const& source, t_qualified_adv_object_type const& type )
	:	t_owned_scriptable_adv_object( source )
{
	set_initial_values( t_creature_type( type.major_subtype() ) );
}

// -------------------------------------------------------------------
// A creature dwelling on the adventure map
// -------------------------------------------------------------------
bool t_adv_dwelling::read_from_map( std::streambuf& buffer, t_progress_handler* handler )
{
	int format_version = get< t_uint16 >( buffer );
	if ( format_version < 0 || format_version > k_current_map_format_version )
		return false;

	if (!t_owned_scriptable_adv_object::read_from_map( buffer, handler, to_base_class_map_format_version( format_version )))
		return false;

	set_initial_values( get_creature_type() );
	if (get_player_color() != k_player_gray)
		clear();

	return true;
}

// -------------------------------------------------------------------
// A creature dwelling on the adventure map
// -------------------------------------------------------------------
int t_adv_dwelling::get_version() const
{
	return k_current_save_format_version;
}

// -------------------------------------------------------------------
// A creature dwelling on the adventure map
// -------------------------------------------------------------------
bool t_adv_dwelling::read( std::streambuf&						stream, 
						   t_qualified_adv_object_type const&	type,
						   int									version )
{
	if (version < 1)
		return true;
	if (!t_owned_scriptable_adv_object::read( stream, type, to_base_class_save_format_version( version )))
		return false;
	if (version < 2)
	{
		set_initial_values( t_creature_type( type.major_subtype() ) );
		return true;
	}
	if (version < 4)
	{
		int discount = get<t_int16>( stream );
		int guards = get<int>( stream );

		if (get_owner_number() < 0)
			set_initial_values( t_creature_type( type.major_subtype() ) );
	}
	else if (version < 6)
	{
		int array_version = get<t_int8>( stream );

		t_creature_array::read_version( stream, array_version );
	}
	else
		t_creature_array::read( stream );

	m_population = get<int>( stream );

	if ( version < 5 )
	{
		// Old method of getting population fractions
		m_population = int( ( unsigned int(m_population) * k_population_divisor ) >> 8 );
	}

	return true;
}

// -------------------------------------------------------------------
// A creature dwelling on the adventure map
// -------------------------------------------------------------------
bool t_adv_dwelling::write( std::streambuf& stream ) const
{
	if (!t_owned_scriptable_adv_object::write( stream ))
		return false;
	t_creature_array::write( stream );
	put<int>( stream, m_population );
	return true;
}


// -------------------------------------------------------------------
// A creature dwelling on the adventure map
// initialize with 1 week's growth
// -------------------------------------------------------------------
void t_adv_dwelling::set_initial_values( t_creature_type creature )
{
	static int k_guard_value[] = { 0, 166, 750, 2000 };

	t_creature_traits const& traits = get_traits( creature );
	int						 guard_value = k_guard_value[traits.level - 1];

	clear();
	if (get_owner_number() < 0 && guard_value > 0)
		add( creature, (guard_value + traits.experience / 2) / traits.experience );
	m_population = traits.weekly_growth * k_population_divisor;
}

// -------------------------------------------------------------------
// A creature dwelling on the adventure map
// grow a given amount and adjust guards
// -------------------------------------------------------------------
void t_adv_dwelling::grow( int amount )
{
	m_population += amount;
}

// -------------------------------------------------------------------
// A creature dwelling on the adventure map
// grow one day's worth
// -------------------------------------------------------------------
void t_adv_dwelling::process_new_day()
{
	if (get_owner_number() < 0)
	{
		t_creature_array::process_new_day( true );
		return;
	}

	t_creature_traits const& traits = get_traits( get_creature_type() );

	// weekly growth is half normal
	grow( traits.weekly_growth * ( k_population_divisor / ( 7 * 2 ) ) );
}

static t_external_string const k_text_no_room( "no_room.dwelling" );
// -------------------------------------------------------------------
// A creature dwelling on the adventure map
// activate trigger
// -------------------------------------------------------------------
void t_adv_dwelling::activate_trigger( t_army* army, t_adv_map_point const& point, 
		                               t_direction direction, t_adventure_frame* frame )
{
	t_player*					owner = army->get_owner();
	std::string					text;
	t_creature_type				creature = get_creature_type();
	t_creature_traits const&	traits = get_traits( creature );
	int							player_number = army->get_owner_number();

	// note that this player has visited.
	if (player_number >= 0)
		m_visited[player_number] = true;

	t_sound_cache music = get_music_playing();

	if (army->is_human())
	{
		stop_music();
		get_dialog_sound( k_dialog_sound_recruit )->play( get_sound_volume() );
	}

	if ( is_ally( owner, get_owner() ) )
	{
		if (army->is_human())
		{
			std::string text = replace_keywords( get_text( "refused_ally"), "%name", get_name() );
			ok_dialog( text, get_dialog_bottom( point ), get_name() );
			play_music( music );
		}
		return;
	}

	if (player_number != get_owner_number() && !empty())
	{
		// create guard army
		t_hero_keyword_replacer replacer;
		t_combat_context_ptr	context;
		
		context = new t_combat_context_adv_object( army, this, point );
		
		if (owner != 0 && !owner->is_computer())
		{
			// notify player of the guards
			t_skill_mastery information = get_information_level();
			bool			approximate = information < k_mastery_advanced;

			replacer.add_creatures( *this, approximate );
			text = replacer( get_text( "guarded" ) );
			if (information >= k_mastery_expert)
				text += "  " + get_outcome_text( *this );

			t_counted_ptr<t_basic_dialog> dialog = new t_basic_dialog;

			dialog->set_text( text );
			dialog->add_creatures( *this, approximate );
			dialog->add_cancel_button();
			dialog->add_ok_button();
			dialog->set_title( get_name() );
			dialog->open( get_dialog_bottom(point), true, k_align_bottom );

			t_sound_cache music = get_music_playing();
			stop_music();
			get_dialog_sound( k_dialog_sound_recruit )->play( get_sound_volume() );
			if (!dialog->run_modal())
				return;	
			play_music( music );
		}
		// perform combat
		run_combat( *context );
		return;
	}
	visit( army, frame );
}

void t_adv_dwelling::on_combat_end( t_army* army, t_combat_result result,
									t_creature_array* losses, t_adventure_frame* frame )
{
	frame->update_army( army );

	if (attacker_lost( result ))
		return;
	
	visit( army, frame );
}

void t_adv_dwelling::visit( t_army* army, t_adventure_frame* frame )
{
	int			player_number = army->get_owner_number();
	t_player*	owner = army->get_owner();

	if ( player_number != get_owner_number() )
	{
		if (army->get_living_hero_count() == 0)
		{
			if (army->is_human())
				ok_dialog( get_text( "no_heroes" ), true );
			return;
		}
		set_owner( player_number );
	}

	t_creature_type				creature = get_creature_type();
	t_creature_traits const&	traits = get_traits( creature );

	if (owner == 0)
		return;

	std::string text;

	if (m_population < k_population_divisor)
	{
		if (owner->is_computer())
			return;
		text = get_text( "empty" );
		text = replace_keywords( text, "%creature_name", traits.plural_name );
		ok_dialog( text, get_dialog_bottom( army->get_position() ), get_name() );
		return;
	}

	if (owner->is_computer())
	{
		t_creature_purchaser purchaser( *this, *army );
		purchaser.go_shopping(*this, *army);
		return;
	}

	if (!army->get_creatures().can_add( creature ))
	{
		text = k_text_no_room;
		text = replace_keywords( k_text_no_room, "%creature_name", traits.plural_name );
		ok_dialog( text, true );
		return;
	}

	// allow recruitment
	run_recruit_dialog( frame, *army );
	frame->update();
}

// ------------------------------------------------------------------------
// Run the recruit creature dialog for this dwelling
// ------------------------------------------------------------------------
void t_adv_dwelling::run_recruit_dialog( t_window * dialog_owner, t_creature_array & destination )
{
	t_creature_type			 creature = get_creature_type();
	t_creature_traits const& traits = get_traits( creature );

	int number = m_population / k_population_divisor;
	int base_cost = traits.cost[k_gold];
	int cost;

	cost = (number * base_cost) / number;
	cost = (cost * (100 - destination.get_recruitment_discount()) + 50) / 100;
	if (cost < 0)
		cost = 0;

	t_counted_ptr<t_recruit_dialog> recruit_dialog;
	t_player&						player = *get_owner();

	recruit_dialog = new t_recruit_dialog( dialog_owner, creature, number, player.get_funds(),
		                                   cost );

	if (!recruit_dialog->run_modal())
		return;

	number = recruit_dialog->get_amount();
	if (number == 0)
		return;
	m_population -= number * k_population_divisor;
	destination.add( creature, number );
	player.spend( recruit_dialog->get_total_cost() );
}

// ------------------------------------------------------------------------
// A creature dwelling on the adventure map
// get help text
// ------------------------------------------------------------------------
void t_adv_dwelling::right_click( t_mouse_event const& event,
							       t_adventure_frame* adventure_frame )
{
	int							  player = get_map()->get_player_number();
	std::string					  text;
	t_window*					  window;
	t_window*					  modal_window = t_window::get_main_window();
	t_screen_point                point = event.screen_point;
	t_counted_ptr<t_basic_dialog> dialog;
	t_skill_mastery					information = get_information_level();
	t_creature_type				  creature = get_creature_type();

	if (!m_visited[player] && information > k_mastery_none)
		m_visited[player] = true;

	point = modal_window->to_client( point );
	window = new t_popup_window( point, modal_window );
	window->set_cursor( adventure_frame->get_cursor( false ));
	dialog = new t_basic_dialog( window );

	if (!m_visited[player])
		text = get_text( "help" );
	else if ( is_ally( get_owner(), &get_map()->get_player() ) )
		text = get_text( "help_ally" );
	else if (m_population < k_population_divisor )
		text = get_text( "help_empty" );
	else if (get_owner_number() == player || empty())
		text = get_text( "help_visited" );
	else
	{
		int			i;
		int			total = 0;
		std::string	guards;
		bool		approximate = information < k_mastery_advanced;

		text = get_text( "help_guarded" );
		dialog->add_creatures( *this, approximate );
		for (i = 0; i < k_size; ++i)
			total += t_creature_array::operator[](i).get_number();

		if (approximate)
			guards = get_army_size_name( total, k_article_a ) + " " 
				   + get_creature_name( creature, 2, false );
		else
			guards = format_string( "%i", total ) + " " 
				   + get_creature_name( creature, total, false );
		text = replace_keywords( text, "%the_guards", guards );
		if (information >= k_mastery_expert)
		{
			text += "  ";
			text += get_outcome_text( *this );
		}
	}

	text = replace_keywords( text, m_population / k_population_divisor, creature, false );
	dialog->set_text( text );
	dialog->open( t_screen_point(0,0), modal_window->get_client_rect() - point, 
		          true, k_align_top );
	window->fit_to_parent_rect();
}

// -----------------------------------------------------------
// keep ownership info in player up to date
// -----------------------------------------------------------
void t_adv_dwelling::set_owner( int player_number )
{
	if (get_owner() != 0)
		get_owner()->remove( this );

	t_owned_scriptable_adv_object::set_owner( player_number );
	
	if (get_owner() != 0)
		get_owner()->add( this );
}

// -----------------------------------------------------------
// keep ownership info in player up to date
// -----------------------------------------------------------
void t_adv_dwelling::initialize( t_adventure_map& map )
{
	t_owned_scriptable_adv_object::initialize( map );

	if ( get_owner_number() >= 0 )
	{
		get_owner()->add( this );
	}
}

// -----------------------------------------------------------
// keep ownership info in player up to date
// -----------------------------------------------------------
void t_adv_dwelling::destroy()
{
	if (get_owner_number() >= 0)
		get_owner()->remove( this );

	t_owned_scriptable_adv_object::destroy();
}

// ---------------------------------------------------------------
// preplacement initialization
// ---------------------------------------------------------------
bool t_adv_dwelling::preplacement( t_adventure_map& map, int pass )
{
	t_player* player = map.get_player( get_player_color() );

	return t_creature_array::preplacement( map, pass, player );
}

// -----------------------------------------------------------
// keep ownership info in player up to date
// -----------------------------------------------------------
void t_adv_dwelling::place( t_adventure_map& map, t_adv_map_point const& point )
{
	t_owned_scriptable_adv_object::place( map, point );

	if (get_owner_number() >= 0)
		get_owner()->add( this );
}

// -----------------------------------------------------------
// -----------------------------------------------------------
float t_adv_dwelling::ai_value( t_adventure_ai const& ai, t_creature_array const& army, int move_cost ) const
{
	t_player*	attacking_owner = army.get_owner();

	if (attacking_owner != get_owner())
	{
		float scale = ai.scale_for_aggression( attacking_owner, get_owner() );
		if (scale == 0.0f)
			return 0.0f;
		if (army.get_living_hero_count() == 0)
			return 0.0f;
		
		float value = 0.0f;

		if ( !empty() )
		{
			float value_drop = value_lost_in_combat(&army, this, true);
			float actual_value_drop = army.ai_value() * value_drop;

			if (value_drop >= 0.95f)
				return 0.0f;

			value -= actual_value_drop;
		}

		t_creature_traits const& traits = get_traits( get_creature_type() );
		int						 growth = (traits.weekly_growth * k_population_divisor ) / 2;

		t_creature creature( get_creature_type(), growth );
		value += creature.ai_value();

		return value * scale;
	}
	else
	{
		t_creature_purchaser purchaser( *this, army );

		float value = purchaser.query_shopping_value();

		if (value < (army.ai_value() / 4))
			return ai.moving_last_army() ? value * 0.05f : 0;
		else
			return value;

	}
}

// -----------------------------------------------------------
// -----------------------------------------------------------
float t_adv_dwelling::ai_activation_value_drop( t_creature_array const& army ) const
{
	if (army.get_owner() == get_owner())
		return 0.0f;
	
	return ::value_lost_in_combat( &army, this, true );
}

// -----------------------------------------------------------
// -----------------------------------------------------------
t_skill_mastery t_adv_dwelling::get_anti_stealth_level() const
{
	return t_creature_array::get_anti_stealth_level();
}

bool t_adv_dwelling::is_triggered_by( t_creature_array const& army ) const
{
	return !army.is_boat() || get_major_subtype() == k_pirate;
}

// -------------------------------------------------------------------
// A random creature dwelling on the adventure map
// -------------------------------------------------------------------
t_random_adv_dwelling::t_random_adv_dwelling( std::string const& model_name )
	:	t_owned_scriptable_adv_object( model_name ),
		m_creature_type( k_peasant )
{
}

// -------------------------------------------------------------------
// A random creature dwelling on the adventure map
// -------------------------------------------------------------------
bool t_random_adv_dwelling::read_from_map( std::streambuf& buffer, t_progress_handler* handler)
{
	int format_version = get< t_uint16 >( buffer );
	if ( format_version < 0 || format_version > k_current_random_map_format_version )
		return false;

	if (!t_owned_scriptable_adv_object::read_from_map( buffer, handler, random_to_base_class_map_format_version( format_version )))
		return false;

	if ( format_version >= 2 )
	{
		if ( !m_linkage_info.read_from_map( buffer ) )
			return false;
	}
	else
	{
		m_linkage_info.reset();
	}

	return true;
}


// -------------------------------------------------------------------
// A random creature dwelling on the adventure map
// -------------------------------------------------------------------
void t_random_adv_dwelling::place( t_adventure_map& map, t_adv_map_point const& point )
{
	t_counted_ptr< t_adv_dwelling > dwelling_ptr = new t_adv_dwelling( m_creature_type, get_player_color() );
	t_owned_scriptable_adv_object::copy_events( *dwelling_ptr, *this );
	dwelling_ptr->place( map, point );
}

// -------------------------------------------------------
// Choose creature type based on link parameters
// -------------------------------------------------------
bool t_random_adv_dwelling::preplacement( t_adventure_map& map, int pass )
{
	if ( t_owned_scriptable_adv_object::preplacement( map, pass ) )
		return true;

	t_linkage_data::t_link_alignment link_alignment = m_linkage_info.get_link_alignment( map, pass );

	if ( link_alignment == t_linkage_data::k_link_alignment_wait )
		return true;

	// Find a creature of the correct alignment
	bool found = false;
	while (!found)
	{
		m_creature_type = pick_creature_type( get_creature_level() );

		if ( link_alignment == t_linkage_data::k_link_alignment_any ||
			get_traits( m_creature_type ).alignment == t_linkage_data::link_alignment_to_town_type( link_alignment ) )
		{
			found = true;
		}
	}

	return false;
}

